package com.example.texted.tencentFaceCheck;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.utils.http.HttpUtils;
import org.apache.commons.lang3.StringUtils;

import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;

public class TencentFaceCheck {
    /**
     * 控制台获取
     */
    private static final String APPID = "APPID";
    /**
     * 控制台获取
     */
    private static final String Secret = "Secret";
    /**
     * 20分钟刷新一次
     */
    private static String TXAccessToken = "accessToken";
    /**
     * 20分钟刷新一次
     */
    private static String TXSignTicket = "SignTicket";


    public static void main(String[] args) throws Exception {
        sendTencentFaceCheckBackToken(APPID, Secret);
        final Map<String, String> url = getFaceCheckUrl("身份证号", "姓名", "https://localhost/");
        System.out.println(url);
    }


    private static void sendTencentFaceCheckBackToken(String tencentFaceCheckID, String tencentFaceCheckSecret) throws Exception {
        String url = "https://miniprogram-kyc.tencentcloudapi.com/api/oauth2/access_token";
        /**
         * 参数：
         * app_id ：业务流程唯一标识，即 wbappid，可参考 获取 WBappid 指引在人脸核身控制台内申请
         * secret：wbappid 对应的密钥，申请 wbappid 时得到，可参考 获取 WBappid 指引在人脸核身控制台内申请
         * grant_type ：授权类型，默认值为：client_credential（必须小写）
         * version：版本号，默认值为：1.0.0
         */
        Map<String, Object> paramsMap = new HashMap<>();
        paramsMap.put("app_id", tencentFaceCheckID);
        paramsMap.put("secret", tencentFaceCheckSecret);
        paramsMap.put("grant_type", "client_credential");
        paramsMap.put("version", "1.0.0");
        String request = HttpUtils.httpGet(url, paramsMap, null);
        JSONObject jsonObject = JSON.parseObject(request);
        /**
         * 参考示例
         * {
         *     "code": "0",
         *     "msg": "请求成功",
         *     "transactionTime": "20151022043831",
         *     "access_token": "accessToken_string",
         *     "expire_time": "20151022043831",
         *     "expire_in": "7200"
         * }
         *
         * 实际返回：
         * {
         *     "transactionTime": "20211209132003",
         *     "expire_in": 7200,
         *     "bizSeqNo": "21120920001184433413200317584371",
         *     "expire_time": "20211209152003",
         *     "code": "0",
         *     "success": true,
         *     "msg": "请求成功",
         *     "access_token": "WAA0f-dGGlQHTVClazkxtmW6FX_nRhpUB01QpWs5MbZluhXlnd4BvbcK_8QIJ21tRpW3WFQnsXY7dOM7Y7QFrC9vag"
         * }
         */
        String code = jsonObject.get("code").toString();
        if ("0".equals(code)) {
            String accessToken = jsonObject.get("access_token").toString();
            String signTicket = getSignTicket(tencentFaceCheckID, accessToken);

            TXAccessToken = accessToken;
            TXSignTicket = signTicket;

            System.out.println("access_token:" + accessToken);
            System.out.println("signTicket:" + signTicket);
        }
    }

    /**
     * SIGN ticket 是合作方后台服务端业务请求生成签名鉴权参数之一，用于后台查询验证结果、调用其他业务服务等。
     *
     * @param tencentFaceCheckID 业务流程唯一标识，即 wbappid，可参考 获取 WBappid 指引在人脸核身控制台内申请
     * @param accessToken 请根据 获取 Access Token 指引进行获取
     * @return signTicket
     */
    public static String getSignTicket(String tencentFaceCheckID, String accessToken) throws Exception {
        String url = "https://miniprogram-kyc.tencentcloudapi.com/api/oauth2/api_ticket";
        Map<String, Object> paramsMap = new HashMap<>();
        paramsMap.put("app_id", tencentFaceCheckID);
        paramsMap.put("access_token", accessToken);
        paramsMap.put("type", "SIGN");
        paramsMap.put("version", "1.0.0");
        String request = HttpUtils.httpGet(url, paramsMap, null);
        JSONObject jsonObject = JSON.parseObject(request);
        String code = jsonObject.getString("code");
        if ("0".equals(code)) {
            JSONArray tickets = jsonObject.getJSONArray("tickets");
            return tickets.getJSONObject(0).getString("value");
        } else {
            throw new Exception(String.format("getSignTicket 失败, %s, %s, %s",
                    tencentFaceCheckID, accessToken, jsonObject.getString("msg")));
        }
    }

    /**
     * NONCE ticket 是合作方前端包含 App 和 H5 等生成签名鉴权参数之一，启动 H5 或 SDK 人脸核身。
     * API ticket 的 NONCE 类型，其有效期为120秒，且一次性有效，即每次启动 SDK 刷脸都要重新请求 NONCE ticket。
     * @param userId 当前使用用户的唯一标识，需合作伙伴自行定义
     * 注意：合作伙伴必须保证 user_id 的全局唯一性，不要带有特殊字符
     * @return nonceTicket
     */
    private static String getNonceTicket(String userId) {
        String accessToken = TXAccessToken;

        String url = "https://miniprogram-kyc.tencentcloudapi.com/api/oauth2/api_ticket";
        Map<String, Object> paramsMap = new HashMap<>();
        paramsMap.put("app_id", APPID);
        paramsMap.put("access_token", accessToken);
        paramsMap.put("type", "NONCE");
        paramsMap.put("version", "1.0.0");
        paramsMap.put("user_id", userId);
        String request = HttpUtils.httpGet(url, paramsMap, null);
        JSONObject jsonObject = JSON.parseObject(request);
        String code = jsonObject.getString("code");
        if ("0".equals(code)) {
            JSONArray tickets = jsonObject.getJSONArray("tickets");
            String value = tickets.getJSONObject(0).getString("value");
            System.out.println(value);
            return value;
        }
        return null;
    }

    public static Map<String, String> getFaceCheckUrl(String cardNo, String name, String callbackUrl) throws Exception {
        Map<String, String> result = new HashMap<>();
        String appId = APPID;
        //调用上送身份信息接口
        String h5faceId;
        String ymdhms = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
        //订单号
        String orderNo = ymdhms + cardNo;
        //调用启动人脸核身
        String userId = "userId" + cardNo;
        String version = "1.0.0";
        String nonce = UUID.randomUUID().toString().replace("-", "");
        Map<String, String> idInfoInBackground = sendIdInfoInBackground(userId, name, cardNo, orderNo);
        String domain = "miniprogram-kyc.tencentcloudapi.com";
        if ("1".equals(idInfoInBackground.get("code"))) {
            h5faceId = idInfoInBackground.get("h5faceId");
            String signTicket = idInfoInBackground.get("signTicket");
            result.put("signTicket", signTicket);
            String optimalDomain = idInfoInBackground.get("optimalDomain");
            if (StringUtils.isNotBlank(optimalDomain)) {
                domain = optimalDomain;
            }
        } else {
            throw new Exception("上送身份信息失败");
        }
        List<String> data = new ArrayList<>();
        data.add(appId);
        data.add(orderNo);
        data.add(userId);
        data.add(version);
        data.add(h5faceId);
        data.add(nonce);
        String nonceTicket = getNonceTicket(userId);
        String sign = sign(data, nonceTicket);
        String stringBuffer = "https://" + domain + "/api/web/login?webankAppId=" + appId +
                "&version=" + version +
                "&nonce=" + nonce +
                "&orderNo=" + orderNo +
                "&h5faceId=" + h5faceId +
                "&url=" + URLEncoder.encode(callbackUrl, "UTF-8") +
                "&from=" + "browser" +
                "&userId=" + userId +
                "&sign=" + sign +
                "&redirectType=1";
        result.put("status", "1");
        result.put("url", stringBuffer);
        return result;

    }

    public static Map<String, String> sendIdInfoInBackground(String userId, String name, String idnum, String orderNo) {
        Map<String, String> resultMap = new HashMap<>();
        resultMap.put("code", "0");
        String faceCheckID = APPID;
        String signTicket = TXSignTicket;
        //日志表记录
        try {
            //生成签名
            /**
             * wbappid	业务流程唯一标识
             * orderNo	订单号，字母/数字组成的字符串，本次人脸核身合作伙伴上送的订单号，唯一标识
             * name	姓名
             * idNo	证件号码
             * userId	用户 ID，用户的唯一标识（不要带有特殊字符）
             * version	1.0.0
             * api ticket	合作伙伴服务端缓存的 tikcet，注意是 SIGN 类型
             */

            List<String> lists = new ArrayList<>();
            lists.add(faceCheckID);
            lists.add(orderNo);//订单号
            lists.add(name);
            lists.add(idnum);
            lists.add(userId);//userID
            lists.add("1.0.0");

            String sign = sign(lists, signTicket);


            //上送身份信息
            /**
             * webankAppId	业务流程唯一标识，即 wbappid，可参考 获取 WBappid 指引在人脸核身控制台内申请	String	8
             * orderNo	订单号，字母/数字组成的字符串，由合作方上送，每次唯一，不能超过32位	String	不能超过32位
             * name	姓名	String	-
             * idNo	证件号码	String	-
             * userId	用户 ID ，用户的唯一标识（不能带有特殊字符），需要跟生成签名的 userId 保持一致	String	不能超过32位
             * version	默认参数值为：1.0.0	String	20
             * sign	签名：使用上文 生成的签名	String	40
             */
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("webankAppId", faceCheckID);
            jsonObject.put("orderNo", orderNo);
            jsonObject.put("name", name);
            jsonObject.put("idNo", idnum);
            jsonObject.put("userId", userId);
            jsonObject.put("version", "1.0.0");
            jsonObject.put("sign", sign);
            String url = "https://miniprogram-kyc.tencentcloudapi.com/api/server/h5/geth5faceid?orderNo=";
            url = url + orderNo;
            String resStr = HttpUtils.httpPost(url, null, jsonObject, null);
            JSONObject resObject = JSON.parseObject(resStr);
            System.out.println(resObject);

            /**
             * 响应参数
             * code	String	0：成功 非0：失败 详情请参见 SaaS 服务错误码
             * msg	String	请求结果描述
             * bizSeqNo	String	请求业务流水号
             * orderNo	String	订单编号
             * h5faceId	String	此次刷脸用户标识
             * transactionTime	String	接口请求的时间
             * optimalDomain	String	启动 H5 人脸核身步骤中调用 login 接口使用的域名
             *
             * 响应示例
             * {
             *    "code":"0",
             *    "msg":"请求成功",
             *    "bizSeqNo":"21062120001184438418322908010297",
             *    "result":{
             *         "bizSeqNo":"21062120001184438418322908010297",
             *         "transactionTime":"20210621183229",
             *         "orderNo":"1617091885609_17432576916585_0",
             *         "h5faceId":"wb0375fa5243984381ea7b7013f13795",
             *         "optimalDomain":"miniprogram-kyc.tencentcloudapi.com",
             *         "success":false
             *              },
             *      "transactionTime":"20210621183229"
             * }
             */
            String code = resObject.get("code").toString();
            if ("0".equals(code)) {
                //请求业务流水号
                String bizSeqNo = resObject.getString("bizSeqNo");
                JSONObject result = resObject.getJSONObject("result");
                //此次刷脸用户标识
                String h5faceId = result.getString("h5faceId");
                //人脸核身步骤中调用 login 接口使用的域名
                String optimalDomain = result.getString("optimalDomain");
                resultMap.put("bizSeqNo", bizSeqNo);
                resultMap.put("h5faceId", h5faceId);
                resultMap.put("optimalDomain", optimalDomain);
                resultMap.put("signTicket", signTicket);
                resultMap.put("code", "1");
            } else {
                resultMap.put("msg", "信息上送失败");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return resultMap;
    }

    public static String sign(List<String> values, String ticket) {
        if (values == null) {
            throw new NullPointerException("values is null");
        }
        values.removeAll(Collections.singleton(null));// remove null
        values.add(ticket);
        java.util.Collections.sort(values);
        StringBuilder sb = new StringBuilder();
        for (String s : values) {
            sb.append(s);
        }
        return DigestUtil.sha1Hex(sb.toString(), CharsetUtil.UTF_8).toUpperCase();
    }
}
